package com.franklin.ideaplugin.mybatisrunner.xml.node;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.franklin.ideaplugin.mybatisrunner.entity.ElExpression;
import com.franklin.ideaplugin.mybatisrunner.utils.MapperUtils;
import com.franklin.ideaplugin.mybatisrunner.utils.MyStringUtils;
import com.franklin.ideaplugin.mybatisrunner.utils.XmlUtils;
import com.franklin.ideaplugin.mybatisrunner.xml.MybatisSqlContext;
import com.franklin.ideaplugin.mybatisrunner.xml.params.ChooseParam;
import com.franklin.ideaplugin.mybatisrunner.xml.params.MybatisParam;
import com.franklin.ideaplugin.mybatisrunner.xml.params.NormalParam;
import com.franklin.ideaplugin.mybatisrunner.xml.params.PredicateParam;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlElement;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.xml.XmlText;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author Ye Junhui
 * @since 2024/2/19
 */
@RequiredArgsConstructor
@Getter
public enum MybatisNodeHandlers {

    Trim {
        @Override
        protected String resolveChildSql(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
            String prefix = XmlUtils.getAttributeValue(xmlTag, "prefix");
            String prefixOverrides = XmlUtils.getAttributeValue(xmlTag, "prefixOverrides");
            String suffix = XmlUtils.getAttributeValue(xmlTag, "suffix");
            String suffixOverrides = XmlUtils.getAttributeValue(xmlTag, "suffixOverrides");
            //子SQL
            String childSql = resolveSql(xmlTag, mybatisSqlContext);
            //前后缀
            childSql = StrUtil.removePrefix(childSql, prefixOverrides);
            childSql = StrUtil.removeSuffix(childSql, suffixOverrides);
            return prefix + "\n" + childSql + "\n" + suffix;
        }
    },
    Where {
        @Override
        protected String resolveChildSql(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
            String childSql = resolveSql(xmlTag, mybatisSqlContext);
            if (StrUtil.isBlank(childSql)) {
                return "";
            }
            childSql = StrUtil.trim(childSql);
            if (StrUtil.startWithAnyIgnoreCase(childSql, "and", "or")) {
                childSql = childSql.substring(3);
            }
            return "WHERE \n" + childSql;
        }
    },
    Set {
        @Override
        protected String resolveChildSql(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
            String childSql = resolveSql(xmlTag, mybatisSqlContext);
            if (StrUtil.startWithAnyIgnoreCase(childSql, "set")) {
                return childSql;
            }
            return "SET " + childSql;
        }
    },
    Foreach {
        @Override
        protected String resolveChildSql(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
            String collection = XmlUtils.getAttributeValue(xmlTag, "collection");
            String item = XmlUtils.getAttributeValue(xmlTag, "item");
            String index = XmlUtils.getAttributeValue(xmlTag, "index");
            String open = XmlUtils.getAttributeValue(xmlTag, "open");
            String close = XmlUtils.getAttributeValue(xmlTag, "close");
            String separator = XmlUtils.getAttributeValue(xmlTag, "separator");

            //参数
            String collectionParamName = getCollectionParamName(collection, item);

            //参数El表达式
            String itemElExpression = mybatisSqlContext.getParamElMap().getOrDefault(collectionParamName, "");
            //索引El表达式
            String indexElExpression = mybatisSqlContext.getParamElMap().getOrDefault(index, "");


            //集合字符串
            String colStr = mybatisSqlContext.getParamMap().getOrDefault(collectionParamName, "");
            List<String> valueList = StrUtil.split(colStr, separator);

            //索引
            AtomicInteger indexer = new AtomicInteger();

            //子Sql
            String childSql = resolveSql(xmlTag, mybatisSqlContext);

            String childSqlTotal = valueList.stream()
                    .filter(StrUtil::isNotBlank)
                    .map(value -> {
                        String tempChildSql = childSql;
                        tempChildSql = StrUtil.replace(tempChildSql, itemElExpression, value);
                        tempChildSql = StrUtil.replace(tempChildSql, indexElExpression, String.valueOf(indexer.getAndIncrement()));
                        return tempChildSql;
                    })
                    .filter(StrUtil::isNotBlank)
                    .collect(Collectors.joining(separator));

            return open + "\n" + childSqlTotal + "\n" + close;
        }

        @Override
        protected List<MybatisParam> resolveTextParams(XmlText xmlText) {
            XmlTag xmlTag = PsiTreeUtil.getParentOfType(xmlText, XmlTag.class);
            List<MybatisParam> mybatisParamList = super.resolveTextParams(xmlText);
            if (Objects.isNull(xmlTag)) {
                return mybatisParamList;
            }
            String collection = XmlUtils.getAttributeValue(xmlTag, "collection");
            String item = XmlUtils.getAttributeValue(xmlTag, "item");
            String index = XmlUtils.getAttributeValue(xmlTag, "index");
            String collectionParamName = getCollectionParamName(collection, item);

            //是否有item
            boolean itemFound = false;
            for (MybatisParam mybatisParam : mybatisParamList) {
                //item
                if (mybatisParam.getParamName().equals(item)) {
                    mybatisParam.setParamName(collectionParamName);
                    itemFound = true;
                }
                //index
                if (mybatisParam.getParamName().equals(index)) {
                    mybatisParam.setVisible(false);
                }
            }

            //没有item创建item
            if (!itemFound){
                NormalParam normalParam = new NormalParam(xmlText);
                normalParam.setElExpression(MyStringUtils.buildElExpression(item));
                normalParam.setParamName(collectionParamName);
                mybatisParamList.add(0, normalParam);
            }

            return mybatisParamList;
        }

        /**
         * 获取集合参数名
         * @param collection
         * @param item
         * @return
         */
        private String getCollectionParamName(String collection, String item) {
            if (StrUtil.isBlank(item)){
                return collection;
            }
            return collection + "." + item;
        }

    },
    If {
        @Override
        protected String resolveChildSql(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
            String test = XmlUtils.getAttributeValue(xmlTag, "test");
            if (mybatisSqlContext.getTestCases().contains(test)) {
                return resolveSql(xmlTag, mybatisSqlContext);
            }
            return "";
        }

        @Override
        protected List<MybatisParam> resolveTagParams(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
            return super.resolveTagParams(xmlTag, mybatisSqlContext);
        }
    },
    Include {
        @Override
        protected String resolveChildSql(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
            String refid = XmlUtils.getAttributeValue(xmlTag, "refid");
            if (StrUtil.isBlank(refid)) {
                return "";
            }
            PsiElement psiElement = mybatisSqlContext.getPsiElement();

            if (!(psiElement instanceof XmlTag)) {
                return "";
            }

            XmlFile mapperFile = null;
            String sqlId = null;
            if (refid.contains(".")) {
                int lastIndex = refid.lastIndexOf(".");
                String className = refid.substring(0, lastIndex);
                sqlId = refid.substring(lastIndex + 1);
                Module module = ModuleUtil.findModuleForPsiElement(psiElement);
                //外部文件引用
                List<XmlFile> xmlFileList = MapperUtils.findMapper(module, className);
                if (CollUtil.isEmpty(xmlFileList)) {
                    return "";
                }
                mapperFile = xmlFileList.get(0);

            }else {
                XmlTag realTag = (XmlTag) psiElement;
                mapperFile = (XmlFile) realTag.getContainingFile();
                sqlId = refid;
            }

            return resolveRefSql(sqlId, mapperFile.getRootTag(), mybatisSqlContext);
        }

        /**
         * 解析引用SQL
         * @param refid
         * @param mapperFileRootTag
         * @param mybatisSqlContext
         * @return
         */
        private String resolveRefSql(
                String refid,
                XmlTag mapperFileRootTag,
                MybatisSqlContext mybatisSqlContext
        ) {
            List<XmlElement> xmlChild = XmlUtils.findXmlChild(mapperFileRootTag);
            for (XmlElement xmlElement : xmlChild) {
                if (xmlElement instanceof XmlTag) {
                    XmlTag xmlTag = (XmlTag) xmlElement;
                    if (xmlTag.getName().equals("sql")) {
                        String sqlId = XmlUtils.getAttributeValue(xmlTag, "id");
                        if (StrUtil.equals(sqlId, refid)) {
                            return resolveSql(xmlTag, mybatisSqlContext);
                        }
                    }
                }
            }
            return "";
        }
    },
    Choose {
        @Override
        protected String resolveChildSql(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
            StringBuilder chooseBuilder = new StringBuilder();
            boolean choose = false;
            List<XmlElement> xmlElementList = XmlUtils.findXmlChild(xmlTag);
            for (XmlElement xmlElement : xmlElementList) {
                if (xmlElement instanceof XmlTag) {
                    XmlTag tag = (XmlTag) xmlElement;
                    //when
                    String name = tag.getName();
                    if (name.equals(When.getNodeName())) {
                        String whenSql = When.resolveChildSql(tag, mybatisSqlContext);
                        if (StrUtil.isNotBlank(whenSql)) {
                            chooseBuilder.append(" ").append(whenSql);
                            choose = true;
                        }
                    } else if (name.equals(Otherwise.getNodeName())) {
                        if (!choose) {
                            chooseBuilder.append(" ").append(resolveSql(tag, mybatisSqlContext));
                        }
                    }
                } else {
                    XmlText xmlText = (XmlText) xmlElement;
                    chooseBuilder.append(" ").append(xmlText.getValue().trim());
                }
            }
            return chooseBuilder.toString();
        }

        @Override
        protected List<MybatisParam> resolveTagParams(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
            Integer chooseIndex = mybatisSqlContext.nextChooseIndex(xmlTag);
            List<XmlElement> xmlElementList = XmlUtils.findXmlChild(xmlTag);
            List<String> expressionList = xmlElementList.stream()
                    .filter(xmlElement -> xmlElement instanceof XmlTag)
                    .map(xmlElement -> (XmlTag) xmlElement)
                    .map(childTag -> {
                        if (childTag.getName().equals(When.getNodeName())) {
                            return XmlUtils.getAttributeValue(childTag, "test");
                        } else {
                            return Otherwise.getNodeName();
                        }
                    })
                    .filter(StrUtil::isNotBlank)
                    .collect(Collectors.toList());

            ChooseParam chooseParam = new ChooseParam(xmlTag, expressionList, chooseIndex);

            chooseParam.setElExpression(Choose.getNodeName() + chooseIndex);
            return Collections.singletonList(chooseParam);
        }

    },
    When {
        @Override
        protected String resolveChildSql(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
            String test = XmlUtils.getAttributeValue(xmlTag, "test");
            XmlTag parentTag = xmlTag.getParentTag();
            Integer index = mybatisSqlContext.getChooseIndexMap().get(parentTag);
            test = Choose.getNodeName() + index + "." + test;
            if (mybatisSqlContext.getTestCases().contains(test)) {
                return resolveSql(xmlTag, mybatisSqlContext);
            }
            return "";
        }

        @Override
        protected List<MybatisParam> resolveTagParams(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
            return Collections.emptyList();
        }

    },
    Otherwise {
        @Override
        protected String resolveChildSql(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
            return resolveSql(xmlTag, mybatisSqlContext);
        }
    },
    Bind {
        @Override
        protected String resolveChildSql(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
            return "";
        }
    },
    Default {
        @Override
        protected String resolveChildSql(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
            return "";
        }
    };


    /**
     * map
     */
    private final static Map<String, MybatisNodeHandlers> nodeHandlerMap = Arrays.stream(values())
            .collect(Collectors.toMap(
                    MybatisNodeHandlers::getNodeName,
                    Function.identity()
            ));

    /**
     * 获取节点名称
     *
     * @return
     */
    protected String getNodeName() {
        return StrUtil.lowerFirst(name());
    }

    /**
     * 解析子SQL
     *
     * @param xmlTag
     * @param mybatisSqlContext
     * @return
     */
    protected abstract String resolveChildSql(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext);

    /**
     * 解析参数
     *
     * @param element
     * @param mybatisSqlContext
     * @return
     */
    protected List<MybatisParam> doResolveParams(XmlElement element, MybatisSqlContext mybatisSqlContext) {
        List<MybatisParam> mybatisParamList = Lists.newArrayList();
        if (element instanceof XmlText) {
            XmlText xmlText = (XmlText) element;
            List<MybatisParam> textParams = resolveTextParams(xmlText);
            CollectionUtil.addAll(mybatisParamList, textParams);
        }
        if (element instanceof XmlTag) {
            XmlTag xmlTag = (XmlTag) element;
            MybatisNodeHandlers mybatisNodeHandler = Optional.ofNullable(nodeHandlerMap.get(xmlTag.getName()))
                    .orElse(MybatisNodeHandlers.Default);
            List<MybatisParam> tagParams = mybatisNodeHandler.resolveTagParams(xmlTag,mybatisSqlContext);
            CollectionUtil.addAll(mybatisParamList, tagParams);
            List<XmlElement> xmlElementList = XmlUtils.findXmlChild(xmlTag);
            xmlElementList.stream()
                    .map(childElement -> mybatisNodeHandler.doResolveParams(childElement, mybatisSqlContext))
                    .flatMap(Collection::stream)
                    .forEach(mybatisParamList::add);
        }
        return mybatisParamList.stream()
                .distinct()
                .collect(Collectors.toList());
    }

    /**
     * 解析参数
     *
     * @param xmlTag
     * @param mybatisSqlContext
     * @return
     */
    public static List<MybatisParam> resolveParams(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
        Set<String> cache = Sets.newLinkedHashSet();
        MybatisNodeHandlers mybatisNodeHandler = Optional.ofNullable(nodeHandlerMap.get(xmlTag.getName()))
                .orElse(MybatisNodeHandlers.Default);
        List<MybatisParam> mybatisParamList = mybatisNodeHandler.doResolveParams(xmlTag,mybatisSqlContext);
        return mybatisParamList.stream()
                .filter(mybatisParam -> cache.add(mybatisParam.getElExpression()))
                .collect(Collectors.toList());
    }

    /**
     * 解析SQL
     *
     * @param xmlTag
     * @param mybatisSqlContext
     * @return
     */
    public static String resolveSql(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
        List<XmlElement> xmlElementList = XmlUtils.findXmlChild(xmlTag);
        String sql = xmlElementList.stream()
                .map(xmlElement -> {
                    if (xmlElement instanceof XmlTag) {
                        XmlTag tag = (XmlTag) xmlElement;
                        return Optional.ofNullable(nodeHandlerMap.get(tag.getName()))
                                .map(mybatisNodeHandler -> mybatisNodeHandler.resolveChildSql(tag, mybatisSqlContext))
                                .orElse("");
                    }
                    XmlText xmlText = (XmlText) xmlElement;
                    return xmlText.getValue().trim();
                })
                .filter(StrUtil::isNotBlank)
                .collect(Collectors.joining("\n"));
        return sql;
    }

    /**
     * 解析标签参数
     *
     * @param xmlTag
     * @param mybatisSqlContext
     * @return
     */
    protected List<MybatisParam> resolveTagParams(XmlTag xmlTag, MybatisSqlContext mybatisSqlContext) {
        String test = XmlUtils.getAttributeValue(xmlTag, "test");
        if (StrUtil.isBlank(test)) {
            return Collections.emptyList();
        }
        PredicateParam predicateParam = new PredicateParam(xmlTag);
        predicateParam.setElExpression(test);
        return Collections.singletonList(predicateParam);
    }

    /**
     * 解析文本参数
     *
     * @param xmlText
     * @return
     */
    protected List<MybatisParam> resolveTextParams(XmlText xmlText) {
        List<ElExpression> elExpressionList = XmlUtils.findElExpressionList(xmlText);
        List<MybatisParam> mybatisParamList = elExpressionList.stream()
                .map(elExpression -> {
                    NormalParam normalParam = new NormalParam(elExpression.getPsiElement());
                    normalParam.setElExpression(elExpression.getExpression());
                    return normalParam;
                })
                .collect(Collectors.toList());
        return mybatisParamList;
    }
}
